package org.budo.jni.user32.impl;

import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

import org.budo.jni.user32.BudoJniUser32;

import com.sun.jna.Native;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.W32Errors;
import com.sun.jna.platform.win32.WinDef.HBITMAP;
import com.sun.jna.platform.win32.WinDef.HDC;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinDef.RECT;
import com.sun.jna.platform.win32.WinGDI;
import com.sun.jna.platform.win32.WinGDI.BITMAPINFO;
import com.sun.jna.platform.win32.WinGDI.BITMAPINFOHEADER;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.platform.win32.WinNT.HRESULT;

import lombok.extern.slf4j.Slf4j;

/**
 * @author limingwei
 */
@Slf4j
public class BudoJniUser32Impl implements BudoJniUser32 {
    private static final User32 USER_32 = User32.INSTANCE;

    private static final BUDO_GDI_32 GDI_32 = BUDO_GDI_32.INSTANCE;

    @Override
    public String foregroundWindowText() {
        HWND foregroundWindow = this.foregroundWindow();
        return this.windowText(foregroundWindow);
    }

    @Override
    public String foregroundWindowClassName() {
        HWND foregroundWindow = this.foregroundWindow();
        return this.windowClassName(foregroundWindow);
    }

    @Override
    public HWND foregroundWindow() {
        return USER_32.GetForegroundWindow();
    }

    @Override
    public String windowText(HWND window) {
        int len = USER_32.GetWindowTextLength(window);
        len = len + 5; // 不知为何，如果直接用len，则尾巴上会少一个字符

        char[] chars = new char[len];
        USER_32.GetWindowText(window, chars, chars.length);

        return Native.toString(chars);
    }

    @Override
    public String windowClassName(HWND window) {
        char[] chars = new char[2048];
        USER_32.GetClassName(window, chars, chars.length);
        return Native.toString(chars);
    }

    @Override
    public Rectangle foregroundWindowLocationAndSize() {
        HWND foregroundWindow = this.foregroundWindow();
        return this.windowLocationAndSize(foregroundWindow);
    }

    @Override
    public Rectangle windowLocationAndSize(HWND window) {
        final RECT lpRect = new RECT();

        boolean getWindowRect = User32.INSTANCE.GetWindowRect(window, lpRect);

        if (getWindowRect) {
            return new Rectangle(lpRect.left, //
                    lpRect.top, //
                    Math.abs(lpRect.right - lpRect.left), //
                    Math.abs(lpRect.bottom - lpRect.top));
        }

        int errCode = Kernel32.INSTANCE.GetLastError();
        HRESULT error = W32Errors.HRESULT_FROM_WIN32(errCode);

        log.debug("#77 windowLocationAndSize error, return null, errCode=" + errCode + ", error=" + error + ", window=" + window);
        return null;
    }

    @Override
    public boolean isFrontWindowInScreen() {
        Rectangle frontWin = this.foregroundWindowLocationAndSize();

        // 位置不在屏幕内
        if (null == frontWin //
                || frontWin.getWidth() <= 0 || frontWin.getHeight() <= 0 //
                || frontWin.getX() < 0 || frontWin.getY() < 0) { // 未在屏幕区域内

            return false;
        }

        return true;
    }

    private static BITMAPINFO BIT_MAP_INFO = new BITMAPINFO();

    private static BITMAPINFOHEADER BMI_HEADER = BIT_MAP_INFO.bmiHeader;

    private static RECT RECT = new RECT();

    private static int OLD_WIDTH = 0;

    private static int OLD_HEIGHT = -1;

    private static ColorModel COLOR_MODEL;

    private static DataBuffer DATA_BUFFER;

    private static WritableRaster WRITABLE_RASTER;

    private static int[] PIXELS;

    private static HDC HDC_WND = null;

    private static HDC HDC_MEM;

    private static HWND LAST_WND = null;

    private static HBITMAP HBM_WND = null;

    public BufferedImage screenCapture(HWND hwnd) {
        if (LAST_WND != hwnd) {
            OLD_WIDTH = -1;
            // get client rect
            if (HDC_WND != null) {
                GDI_32.DeleteDC(HDC_MEM);
                USER_32.ReleaseDC(hwnd, HDC_WND);
            }

            HDC_WND = GDI_32.GetDC(hwnd);
            HDC_MEM = GDI_32.CreateCompatibleDC(HDC_WND);
            LAST_WND = hwnd;
        }

        User32.INSTANCE.GetWindowRect(hwnd, RECT);
        int width = RECT.right - RECT.left;
        int height = RECT.bottom - RECT.top;
        boolean reload = (width != OLD_WIDTH || height != OLD_HEIGHT);

        // 通过内存DC复制客户区到DDB位图
        if (reload) {
            if (HBM_WND != null) {
                GDI_32.DeleteObject(HBM_WND);
            }
            HBM_WND = GDI_32.CreateCompatibleBitmap(HDC_WND, width, height);
        }

        HANDLE oldBitmap = GDI_32.SelectObject(HDC_MEM, HBM_WND);
        GDI_32.BitBlt(HDC_MEM, 0, 0, width, height, HDC_WND, 0, 0, BUDO_GDI_32.SRCCOPY);
        GDI_32.SelectObject(HDC_MEM, oldBitmap);

        if (reload) {
            // 信息头
            BMI_HEADER.biSize = 40;
            BMI_HEADER.biWidth = width;
            BMI_HEADER.biHeight = -height;
            BMI_HEADER.biPlanes = 1;
            BMI_HEADER.biBitCount = 32; // 按照每个像素用32bits表示转换

            int strideBits;
            int strideBytesAligned;
            int strideElementsAligned;

            strideBits = (BMI_HEADER.biWidth * BMI_HEADER.biBitCount);
            strideBytesAligned = (((strideBits - 1) | 0x1F) + 1) >> 3;
            strideElementsAligned = strideBytesAligned / 4;

            COLOR_MODEL = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF);
            DATA_BUFFER = new DataBufferInt(strideElementsAligned * height);

            // buffer.
            WRITABLE_RASTER = Raster.createPackedRaster(DATA_BUFFER, width, height, strideElementsAligned, ((DirectColorModel) COLOR_MODEL).getMasks(), null);
            PIXELS = ((DataBufferInt) DATA_BUFFER).getData();
        }

        boolean oks = GDI_32.GetDIBits(HDC_MEM, HBM_WND, 0, height, PIXELS, BIT_MAP_INFO, WinGDI.DIB_RGB_COLORS);
        if (oks) {
            return new BufferedImage(COLOR_MODEL, WRITABLE_RASTER, false, null);
        }

        log.error("#185 return null");
        return null;
    }
}
